package com.life.statistics;

import java.text.DecimalFormat;
import java.util.ArrayList;

import com.life.constants.Constants;
import com.life.rule.ILifeRule;
import com.life.utils.LoggerUtil;
import com.math.ArrayMath;

public class StatisticStruct
{
    final static int MISSING = -999;

    public StatisticsElement elements[][] = null;

    boolean calculated = false;

    double sum = MISSING;

    double lifeMean = MISSING;

    double lifeStandardDeviation = MISSING;

    double deathMean = MISSING;

    double deathStandardDeviation = MISSING;

    
    double q1Birth = MISSING;

    double q2Birth = MISSING;

    double q3Birth = MISSING;

    double q4Birth = MISSING;

    double q1Death = MISSING;

    double q2Death = MISSING;

    double q3Death = MISSING;

    double q4Death = MISSING;

    double startPopulation = MISSING;

    double endingPopulation = MISSING;

    double birthDeathCorrelation = MISSING;
    
    double totalBirths = MISSING;
    double totalDeaths = MISSING;
    double averageBirths = MISSING;
    double averageDeaths = MISSING;
    double generations = MISSING;
    
    double minLife = MISSING;
    double maxLife = MISSING;
    
    double cov = MISSING;
    
    int[]lifes = null;
    int[]deaths = null;
    ILifeRule[]rules = null;

    public double siqrBirth;

    public double siqrDeath;
    
    public double coefficientOfSkewness = MISSING;
    public double deathCoefficientOfSkewness = MISSING;
    
    
    public double meanAbsDeviation = MISSING;
    public double deathMeanAbsDeviation = MISSING;
    
    DecimalFormat formatter = new DecimalFormat("#.000");
    
    public String format(double val)
    {
	return formatter.format(val);
    }
    
    public int[] getLifes()
    {
	process();
	return lifes;
    }
    
    public int[] getDeaths()
    {
	process();
	return deaths;
    }
    
    public double getBirthMean()
    {
	process();
	return lifeMean;
    }
    
    
    public double getDeathMean()
    {
	process();
	return deathMean;
    }

    public double getSum()
    {
	process();
	return sum;
    }

    public void process()
    {
	if (!calculated)
	{
	    calculate();
	}
    }
    
    public  StatisticsElement[] flattenElements(StatisticsElement[][]val)
    {
	
	StatisticsElement[] flattened = new StatisticsElement[val.length * val[0].length];
	int counter = 0;
	for(int i = 0; i < val.length; i++)
	{
	    for(int j =0; j < val[i].length;j++)
	    {
		flattened[counter++]=val[i][j];
	    }
	}
	
	return flattened;
    }

    public void calculate()
    {

	if (!calculated && elements != null)
	{
	    int size = LoggerUtil.getSize();
	    
	    lifes = new int[size];
	    deaths = new int[size];
	    rules = new ILifeRule[size];
	    
	    int total = elements.length*elements[0].length;
	    
	    for(int i = 0; i < size; i++)
	    {
		lifes[i] = LoggerUtil.getLife(i);
		deaths[i]=total-lifes[i];
		rules[i] = LoggerUtil.getRule(i);
	    }
	    
	    int [] sortedLife = ArrayMath.sortMinToMax(lifes);
	    int [] sortedDeath = ArrayMath.sortMinToMax(deaths);
	    
	    lifeMean = ArrayMath.mean(lifes);
	    lifeStandardDeviation = ArrayMath.standardDeviation(lifes);
	    
	    deathMean = ArrayMath.mean(deaths);
	    deathStandardDeviation = ArrayMath.standardDeviation(deaths);
	    
	    birthDeathCorrelation = ArrayMath.correlation(deaths,lifes);
	    
	    cov = lifeStandardDeviation/lifeMean; 
	    
	    q1Birth = sortedLife[(int)(size/4)-1];
	    q1Death = sortedDeath[(int)(size/4)-1];
	    
	    q2Birth = sortedLife[(int)(size/2)-1];
	    q2Death = sortedDeath[(int)(size/2)-1];
	    
	    q3Birth = sortedLife[(int)(3*size/4)-1];
	    q3Death = sortedDeath[(int)(3*size/4)-1];
	    
	    q3Birth = sortedLife[(int)(3*size/4)-1];
	    q3Death = sortedDeath[(int)(3*size/4)-1];
	    
	    q4Birth = sortedLife[size-1];
	    q4Death = sortedDeath[size-1];
	    
	    siqrBirth = (q3Birth-q1Birth)/2;
	    siqrDeath = (q3Death-q1Death)/2;
	    
	    coefficientOfSkewness = ArrayMath.coefficientofSkewness(lifes);
	    deathCoefficientOfSkewness = ArrayMath.coefficientofSkewness(deaths);
	    
	    meanAbsDeviation = ArrayMath.meanAbsDeviation(lifes);
	    deathMeanAbsDeviation = ArrayMath.meanAbsDeviation(deaths);
	    
	    
	    /* calculate statistics of board*/
	    calculated = true;
	    
	   
	}
    }

    public double getQ1Birth()
    {
	process();
	return q1Birth;
    }
    
    public ILifeRule[] getRules()
    {
	process();
	return rules;
    }
    

    public double getQ1Death()
    {
	process();
	return q1Death;
    }

    public double getQ2Birth()
    {
	process();
	return q2Birth;
    }

    public double getQ2Death()
    {
	process();
	return q2Death;
    }

    public double getQ3Birth()
    {
	process();
	return q3Birth;
    }

    public double getQ3Death()
    {
	process();
	return q3Death;
    }

    public double getQ4Birth()
    {
	process();
	return q4Birth;
    }

    public double getQ4Death()
    {
	process();
	return q4Death;
    }

    public double getLifeStandardDeviation()
    {
	process();
	return lifeStandardDeviation;
    }
    
    public double getDeathStandardDeviation()
    {
	process();
	return deathStandardDeviation;
    }

    public double getEndingPopulation()
    {
	process();
	return endingPopulation;
    }

    public double getStartPopulation()
    {
	process();
	return startPopulation;
    }

    public void setElements(StatisticsElement[][] elements)
    {
	/* reset that we have calculated the elements since new elements are being added*/
	calculated = false;
	this.elements = elements;
    }

    public void setEndingPopulation(double endingPopulation)
    {
        this.endingPopulation = endingPopulation;
    }

    public void setStartPopulation(double startPopulation)
    {
        this.startPopulation = startPopulation;
    }

    /**
     * @return the totalBirths
     */
    public double getTotalBirths()
    {
        return totalBirths;
    }

    /**
     * @param totalBirths the totalBirths to set
     */
    public void setTotalBirths(double totalBirths)
    {
        this.totalBirths = totalBirths;
    }

    /**
     * @return the totalDeaths
     */
    public double getTotalDeaths()
    {
        return totalDeaths;
    }

    /**
     * @param totalDeaths the totalDeaths to set
     */
    public void setTotalDeaths(double totalDeaths)
    {
        this.totalDeaths = totalDeaths;
    }

    /**
     * @return the averageBirths
     */
    public double getAverageBirths()
    {
        return averageBirths;
    }

    /**
     * @return the averageDeaths
     */
    public double getAverageDeaths()
    {
        return averageDeaths;
    }

    /**
     * @return the generations
     */
    public double getGenerations()
    {
        return generations;
    }

    /**
     * @param generations the generations to set
     */
    public void setGenerations(double generations)
    {
        this.generations = generations;
    }
    
    public ArrayList<String> getReportList()
    {
	ArrayList<String> results = new ArrayList<String>(Constants.SMALL_COLLECTION_SIZE);
	
	process();
	
	
	if(this.getBirthMean() != MISSING)
	{
	    results.add("Life Mean: " + format(getBirthMean()));
	}

	if(this.getLifeStandardDeviation() != MISSING)
	{
	    results.add("Life Standard Deviation: " + format(getLifeStandardDeviation()));
	}
	
	if(this.getDeathMean() != MISSING)
	{
	    results.add("Death Mean: " + format(getDeathMean()));
	}

	if(this.getDeathStandardDeviation() != MISSING)
	{
	    results.add("Death Standard Deviation: " + format(getDeathStandardDeviation()));
	}
	
	results.add("---------------------------------------");
	
	if(this.getBirthDeathCorrelation() != MISSING)
	{
	    results.add("Birth/Death Correlation: " + format(getBirthDeathCorrelation()));
	}
	
	if(this.getCov() != MISSING)
	{
	    results.add("Coefficient of Variance: " + format(getCov()));
	}
	
	if(this.getCoefficientOfSkewness() != MISSING)
	{
	    results.add("Life Coefficient of Skewness: " + format(getCoefficientOfSkewness()));
	}
	
	if(this.getMeanAbsDeviation() != MISSING)
	{
	    results.add("Life Mean Absolute Deviation: " + format(getMeanAbsDeviation()));
	}
	
	if(this.getDeathCoefficientOfSkewness() != MISSING)
	{
	    results.add("Death Coefficient of Skewness: " + format(getDeathCoefficientOfSkewness()));
	}
	
	if(this.getDeathMeanAbsDeviation() != MISSING)
	{
	    results.add("Death Mean Absolute Deviation: " + format(getDeathMeanAbsDeviation()));
	}
	
	results.add("---------------------------------------");
	
	if(this.getStartPopulation() != MISSING)
	{
	    results.add("Starting Population: " + format(getStartPopulation()));
	}
	
	if(this.getEndingPopulation() != MISSING)
	{
	    results.add("Ending Population: " + format(getEndingPopulation()));
	}	
	results.add("---------------------------------------");
	
	if(getTotalBirths() != MISSING)
	{
	   results.add("Total Births: " + format(getTotalBirths())); 
	}
	
	if(getTotalDeaths() != MISSING)
	{
	   results.add("Total Deaths: " + format(getTotalDeaths())); 
	}
	
	
	if(getAverageBirths() != MISSING)
	{
	   results.add("Average Births: " + format(getAverageBirths())); 
	}
	
	if(getAverageDeaths() != MISSING)
	{
	   results.add("Average Deaths: " + format(getAverageDeaths())); 
	}
	
	if(getMinLife() != MISSING)
	{
	    results.add("Minimum Life: " + format(getMinLife())); 
	}
	
	if(getMaxLife() != MISSING)
	{
	   results.add("Maximum Life: " +  format(getMaxLife()));   
	}
	results.add("---------------------------------------");
	
	if(this.getGenerations() != MISSING)
	{
	    results.add("Generations: " + format(getGenerations()));
	}
	results.add("---------------------------------------");
	
	if(this.getQ1Birth() != MISSING)
	{
	   results.add("Q1 Births: " +format(getQ1Birth()));    
	}
	
	if(this.getQ2Birth() != MISSING)
	{
	   results.add("Q2 Births: " + format(getQ2Birth()));    
	}
	
	if(this.getQ3Birth() != MISSING)
	{
	   results.add("Q3 Births: " + format(getQ3Birth()));    
	}
	
	if(this.getQ4Birth() != MISSING)
	{
	   results.add("Q4 Births: " +format( getQ4Birth()));    
	}

	if(this.getQ1Death() != MISSING)
	{
	   results.add("Q1 Deaths: " +format( getQ1Death()));    
	}
	
	if(this.getQ2Death() != MISSING)
	{
	   results.add("Q2 Deaths: " +format( getQ2Death()));    
	}
	
	if(this.getQ3Death() != MISSING)
	{
	   results.add("Q3 Deaths: " +format( getQ3Death()));    
	}
	
	if(this.getQ4Death() != MISSING)
	{
	   results.add("Q4 Deaths: " +format( getQ4Death()));    
	}
	
	if(this.getSiqrBirth() != MISSING)
	{
	   results.add("SIQR Births: " +format( getSiqrBirth()));    
	}
	
	if(this.getSiqrDeath() != MISSING)
	{
	   results.add("SIQR Deaths: " +format( getSiqrDeath()));    
	}
	
	return results;
    }


    /**
     * @return the birthDeathCorrelation
     */
    public double getBirthDeathCorrelation()
    {
	process();
        return birthDeathCorrelation;
    }

    public double getMaxLife()
    {
        return maxLife;
    }

    public void setMaxLife(double maxLife)
    {
        this.maxLife = maxLife;
    }

    public double getMinLife()
    {
        return minLife;
    }

    public void setMinLife(double minLife)
    {
        this.minLife = minLife;
    }

    public double getCov()
    {
	process();
        return cov;
    }

    public void setCov(double cov)
    {
        this.cov = cov;
    }

    public double getSiqrBirth()
    {
	process();
        return siqrBirth;
    }

    public void setSiqrBirth(double siqrBirth)
    {
        this.siqrBirth = siqrBirth;
    }

    public double getSiqrDeath()
    {
	process();
        return siqrDeath;
    }

    public void setSiqrDeath(double siqrDeath)
    {
        this.siqrDeath = siqrDeath;
    }

    public double getCoefficientOfSkewness()
    {
	process();
        return coefficientOfSkewness;
    }

    public void setCoefficientOfSkewness(double coefficientOfSkewness)
    {
        this.coefficientOfSkewness = coefficientOfSkewness;
    }

    public double getMeanAbsDeviation()
    {
	process();
        return meanAbsDeviation;
    }

    public void setMeanAbsDeviation(double meanAbsDeviation)
    {
        this.meanAbsDeviation = meanAbsDeviation;
    }

    public double getDeathCoefficientOfSkewness()
    {
	process();
        return deathCoefficientOfSkewness;
    }

    public void setDeathCoefficientOfSkewness(double deathCoefficientOfSkewness)
    {
        this.deathCoefficientOfSkewness = deathCoefficientOfSkewness;
    }

    public double getDeathMeanAbsDeviation()
    {
	process();
        return deathMeanAbsDeviation;
    }

    public void setDeathMeanAbsDeviation(double deathMeanAbsDeviation)
    {
        this.deathMeanAbsDeviation = deathMeanAbsDeviation;
    }

}
